#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "../util/macros.h"
#include "../util/msg.h"
#include "matrix.h"

Matrix * matrix_new(int rows, int columns) {
	Matrix * M;

	/* alloc */
	M = (Matrix *) calloc(1, sizeof (Matrix));
	if (!M) return NULL;

	M->data = (double *) calloc(columns * rows, sizeof (double));

	/* constructor */
	if (!M->data) {
		free(M);
		return NULL;
	}

	M->columns = columns;
	M->rows = rows;
	M->size = columns * rows;
	return M;
}

Matrix * matrix_new_sequence(int length, double increment) {
	int i;
	double inc;

	Matrix * M = matrix_new(1, length);
	
	for (i = 0, inc = 0; i < M->size; i++, inc += increment) M->data[i] = inc;

	return M;
}

Matrix * matrix_new_square(int dim) {
	return matrix_new(dim, dim);
}

Matrix * matrix_new_identity(int dim) {
	int i;
	Matrix * M;

	M = matrix_new(dim, dim);
	for (i = 0; i < dim; i++) {
		M->data[i * dim + i] = 1.0;
	}
	return M;
}

Matrix * matrix_new_ones(int dim) {
	int i;
	Matrix * M;

	M = matrix_new(dim, dim);
	for (i = 0; i < M->size; i++) {
		M->data[i] = 1.0;
	}
	return M;
}

Matrix * matrix_new_rand(int rows, int columns, double min, double max) {
	int i;
	Matrix * M;

	M = matrix_new(rows, columns);
	for (i = 0; i < M->size; i++) {
		M->data[i] = (double) (min + (rand() * (max - min)) / (double) RAND_MAX);
	}
	return M;
}

Matrix * matrix_new_copy(const Matrix * M) {
	Matrix * C;
	int idx;
	C = matrix_new(M->rows, M->columns);

	for (idx = 0; idx < M->size; ++idx) {

		C->data[idx] = M->data[idx];
	}
	return C;
}

void matrix_clear(Matrix * A) {
	int r;
	for (r = 0; r < A->size; r++) {
		A->data[r] = 0.0;
	}
}

void matrix_copy(const Matrix * src, Matrix ** dest) {

	int idx;

	_verify(dest);
	if (*dest == NULL) *dest = matrix_new(src->rows, src->columns);

	for (idx = 0; idx < src->size; idx++) {
		(*dest)->data[idx] = src->data[idx];
	}
}

void matrix_delete(Matrix * M) {
	if (!M) {
		return;
	}

	free(M->data);
	free(M);
}

void matrix_print(const Matrix * M) {
	int c, r, _r, idx;

	if (M == NULL) {
		printf("Matrix is Null\n");
		return;
	}

	for (r = 0; r < M->rows; r++) {
		_r = r * M->columns;
		for (c = 0; c < M->columns; c++) {
			idx = _r + c;
			printf("%.3f\t", M->data[idx]);
		}
		printf("\n");
	}
	printf("\n");
}

Matrix * matrix_cut(const Matrix * A, int r0, int c0, int r1, int c1) {
	Matrix * cut;

	cut = matrix_new(r1 - r0 + 1, c1 - c0 + 1);

	int r, c;
	for (r = 0; r < cut->rows; r++) {
		for (c = 0; c < cut->columns; c++) {
			cut->data[ r * cut->columns + c ] = A->data[ (r0 + r) * A->columns + (c0 + c) ];
		}
	}
	return cut;
}

void matrix_normailze(Matrix * A, double min, double max) {
	long int i;
	double MAX;
	double MIN;

	double k;

	MAX = MIN = A->data[0];

	for (i = 0; i < A->size; i++) {
		MAX = A->data[i] > MAX ? A->data[i] : MAX;
		MIN = A->data[i] < MIN ? A->data[i] : MIN;
	}

	k = (max - min) / (MAX - MIN);

	for (i = 0; i < A->size; i++) {
		A->data[i] = (A->data[i] - MIN) * k + min;
	}

}

void matrix_get_normailzed(Matrix * A, Matrix ** B, double min, double max) {
	long int i;
	double MAX;
	double MIN;

	double k;

	_verify(B);
	if (*B == NULL) *B = matrix_new(A->rows, A->columns);

	MAX = MIN = A->data[0];

	for (i = 0; i < A->size; i++) {
		MAX = A->data[i] > MAX ? A->data[i] : MAX;
		MIN = A->data[i] < MIN ? A->data[i] : MIN;
	}

	k = (max - min) / (MAX - MIN);

	for (i = 0; i < A->size; i++) {
		(*B)->data[i] = (A->data[i] - MIN) * k + min;
	}

}

void matrix_print3x3(const Matrix * M, int x, int y) {
	int c, r, _r, idx;

	if (M == NULL) {
		printf("Matrix is Null\n");
		return;
	}

	for (r = y - 1; r <= y + 1; r++) {
		_r = r * M->columns;
		for (c = x - 1; c <= x + 1; c++) {
			idx = _r + c;
			printf("%.3f\t", M->data[idx]);
		}
		printf("\n");
	}
	printf("\n");
}
